#include "Col.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "TextFormatter.h"

#include "Class.h"
#include "Text.h"
#include "TextPainter.h"
#include "ObjArray.h"
#include "Math.h"

//---- TextFormatter -----------------------------------------------------------

NewAbstractMetaImpl0(TextFormatter,Object);

LineMark **TextFormatter::lines= 0;
int TextFormatter::nLines= 0;
int TextFormatter::nMarks= 0;
int TextFormatter::nAlloc= 0;

ONEXIT(TextFormatter)
{
	TextFormatter::Free();
}

TextFormatter::TextFormatter()
{
	if (lines == 0)
		lines= new LineMark*[nAlloc= 32];
}

void TextFormatter::Free()
{
	if (lines) {
		for (int i= 0; i < nMarks; i++)
			SafeDelete(lines[i]);
		SafeDelete(lines);
	}
}

int TextFormatter::Format(Text *t, TextPainter *tp, int w, int from, int to)
{
	nLines= 0;
	DoFormat(t, tp, w, from, to);
	return nLines;
}

void TextFormatter::AddBreak(Text *t, TextPainter *tp, int from, int to, LineDesc &ld)
{
	if (nLines >= nAlloc) {
		lines= (LineMark**)Storage::ReAlloc(lines, 2*nAlloc*sizeof(LineMark*));
		nAlloc= 2*nAlloc;
	}
	LineMark *lm= lines[nLines];
	if (nLines >= nMarks) {
		lm= new LineMark;
		lines[nLines]= lm;
		nMarks++;
	} else
		lm= lines[nLines];
	tp->LineHeight(t, ld, from, to);
	lm->ChangeMark(from, to-from, ld);
	ld.Reset();
	nLines++;
}

LineMark *TextFormatter::LineAt(int i)
{
	if (i < 0 || i >= nMarks)
		Error("LineAt", "out of range");
	return lines[i];
}

void TextFormatter::DoFormat(Text*, TextPainter*, int, int, int)
{
	AbstractMethod("DoFormat");
}

//---- SimpleFormatter ---------------------------------------------------------

NewMetaImpl0(SimpleFormatter,TextFormatter);

SimpleFormatter::SimpleFormatter() : TextFormatter()
{
}

void SimpleFormatter::DoFormat(Text *t, TextPainter *tp, int, int from, int to)
{
	AutoTextIter next(t, from, to);
	register int ch, pos;
	int start= from;
	int wx= 0;
	int cx= 0;
	LineDesc maxld, ld;
		
	while (TRUE) {
		ch= next->Token(&wx, &ld);
		maxld.Max(ld);
		if (ch == cEOT) {
			pos= next->GetPos();
			if (start != pos)
				AddBreak(t, tp, start, pos, maxld);
			break;
		}
		if (ch == '\t')
			wx= tp->Tabulate(t, cx, next->GetLastPos(), start);
		if (Text::IsBreak(ch)) {
			AddBreak(t, tp, start, next->GetPos(), maxld);
			start= next->GetPos();
			cx= 0;
		} else
			cx+= wx;
	}
}

//---- FoldingFormatter --------------------------------------------------------

NewMetaImpl(FoldingFormatter,TextFormatter, (T(width), TP(text), T(start),
							T(end), T(nWords), T(wx), T(cx)));

FoldingFormatter::FoldingFormatter() : TextFormatter()
{
}

void FoldingFormatter::DoFormat(Text *t, TextPainter *tp, int w, int from, int to)
{
	AutoTextIter next(t, from, to);
	text= t;
	register int ch, pos;
	start= from;
	nWords= wx= cx= 0;
	LineDesc maxld, ld;
		
	width= tp->GetFormatWidth(t, from, w);

	while (TRUE) {
		ch= next->Token(&wx, &ld);
		
		// break character
		if (wx > width && (next->GetPos()-next->GetLastPos() == 1)) {
			Break(tp, start, next->GetLastPos(), maxld, w);
			BreakCharacter(t, tp, start, maxld, w);
			continue;
		}
		// word has to be folded on several lines
		if (cx + wx > width && nWords == 0) {
			end= next->GetPos();
			BreakWord(t, tp, maxld, w);
			wx= 0;
		}

		if (ch == cEOT) {
			maxld.Max(ld);
			pos= next->GetPos();
			if (start != pos)
				AddBreak(t, tp, start, pos, maxld);
			break;
		}

		if (ch == '\t' || ch == ' ' || Text::IsBreak(ch)) {
			maxld.Max(ld);
			if (ch == '\t')
				wx= tp->Tabulate(t, cx, next->GetLastPos(), start);
			if (Text::IsBreak(ch) || (cx + wx > width))
				Break(tp, start, next->GetPos(), maxld, w);
			else {
				cx+= wx;
				nWords++;
			}
		} else if (cx + wx > width) {
			Break(tp, start, next->GetLastPos(), maxld, w);
			maxld= ld;
			cx= wx;
		} else {
			maxld.Max(ld);
			cx+= wx;
			nWords++;
		}
	}
}

void FoldingFormatter::Break(TextPainter *tp, int from, int to,
											LineDesc &maxld, int w)
{
	AddBreak(text, tp, from, to, maxld);
	start= end= to;
	width= tp->GetFormatWidth(text, end, w);
	cx= 0;
	nWords= 0;
}

void FoldingFormatter::BreakWord(Text*, TextPainter *tp, LineDesc &maxld, int w)
{
	int cw, ccw= 0;
	AutoTextIter ti(text, start, end); // avoid nesting of nextc
	LineDesc ld;
	maxld.Reset();
	while (ti(&cw, &ld) != cEOT) {
		if (ccw + cw > width) {
			end= ti->GetPos();
			ccw= cw;
			Break(tp, start, end-1, maxld, w);
			maxld= ld;
		} else {
			maxld.Max(ld);
			ccw+= cw;
		}
	}
	cx= ccw;
	end= ti->GetPos();
}

void FoldingFormatter::BreakCharacter(Text*, TextPainter *tp, int at,
								LineDesc &maxld, int w)
{
	int cw;
	AutoTextIter ti(text, at, at+1);
	maxld.Reset();
	ti(&cw, &maxld);
	Break(tp, at, at+1, maxld, w);
}

//---- abstract class TextPager ------------------------------------------

NewMetaImpl0(TextPager,Object);

TextPager::TextPager()
{
}

Rectangle TextPager::NextPageBreak(int, const Rectangle &pgr, StaticTextView*)
{
	return pgr;
}

void TextPager::Repaginate(int)
{
}

//---- LinePager ---------------------------------------------------------

#include "StaticTView.h"

LinePager::LinePager()
{
}

Rectangle LinePager::NextPageBreak(int, const Rectangle &rr, StaticTextView *tv)
{
	Rectangle r(rr);
	int sl= tv->PointToLine(r.origin-tv->GetInnerOrigin());
	int h= tv->LineToPoint(sl, FALSE, FALSE).y-r.origin.y;
	int sh= h;
	int ph= r.Height();

	for (int i= sl; i < tv->Lines(); i++) {
		int dh= tv->LineHeight(i);
		if (h + dh > ph) { // possible page break
			if (h != sh)  // page is not empty
				r.extent.y= h;
			break;
		}
		h+= dh;
	}
	return r;
}

//---- LineDesc --------------------------------------------------------

LineDesc::LineDesc()
{
	lnAscent= lnHeight= 0;
}

LineDesc::LineDesc(int b, int h)
{
	lnAscent= b;
	lnHeight= h;
}

void LineDesc::FromFont(Font *f)
{
	lnAscent= f->Ascender();
	lnHeight= f->Spacing();
}

void LineDesc::Reset()
{
	lnAscent= lnHeight= 0;
}

void LineDesc::Max(Font *f)
{
	int a= Math::Max(f->Ascender(), lnAscent);
	int d= Math::Max(lnHeight-lnAscent, f->Spacing()-f->Ascender());
	lnAscent= a;
	lnHeight= a+d;
}

void LineDesc::Max(LineDesc ld)
{
	int a= Math::Max(ld.lnAscent, lnAscent);
	int d= Math::Max(lnHeight-lnAscent, ld.lnHeight - ld.lnAscent);
	lnAscent= a;
	lnHeight= a+d;
}

void LineDesc::Max(int ascent, int height)
{
	int a= Math::Max(ascent, lnAscent);
	int d= Math::Max(height-ascent, lnHeight-lnAscent);
	lnAscent= a;
	lnHeight= a+d;
}

//---- LineMark ----------------------------------------------------------------

NewMetaImpl0(LineMark,Mark);

int LineMark_lineChanged;

LineMark::LineMark(LineDesc ldesc, int pos, int len, eMarkState s)
												: ld(ldesc), Mark(pos, len, s)
{
}

LineMark::LineMark()
{
}

void LineMark::Copy(LineMark *m)
{
	Mark::ChangeMark(m->pos, m->len, m->state);
	ld= m->ld;
}

void LineMark::ChangeMark( int pos, int len, LineDesc ldesc, eMarkState s)
{
	Mark::ChangeMark(pos, len, s);
	ld= ldesc;
}

bool LineMark::IsDifferent(LineMark *m)
{
	return pos != m->pos
		|| len != m->len
		|| !ld.IsEqual(m->ld)
		|| m->state != eStateNone
		|| state != eStateNone;
}
